#include "Compiler.h"
#include "Containers/String.h"

#include "ScriptCompiler/ScriptFileContext.h"
#include "ScriptCompiler/CompilerContext.h"
#include "ScriptCompiler/SemanticAnalyzer/SemanticAnalyzer.h"
#include "Token/TokenGenerator.h"

#include "File/FileUtils.h"

#include "ScriptCompiler/ASTBuilder/ASTBuilder.h"

#include "ScriptCompiler/GenerateByteCode.h"

#include "ScriptRuntime/ByteCode/ByteCodeFile.h"

CCompileResult WH::Compile(const TVector<SPath>& CSFilePaths )
{

    CCompileResult Result;

    auto CompilerContext= std::make_shared<CCompilerContext>();
    for(auto& FilePath : CSFilePaths)
    {
        CString FileContent=SFileUtils::ReadToString(FilePath);
        auto FileContext=std::make_shared<CScriptFileContext>(CompilerContext,FileContent,FilePath);
        if(CompilerContext->ErrorInfo.IsErrorOccured())
        {
            Result.ErrorInfo=CompilerContext->ErrorInfo;
            return Result;
        }
        CompilerContext->ScriptFiles.Add(FilePath,FileContext);
    }
   
    //2 .scanner , convert everything in to a token list
    for(auto& FileContext : CompilerContext->ScriptFiles.Values())
    {
        CTokenGenerator(FileContext).GenerateTokens();
        if(CompilerContext->ErrorInfo.IsErrorOccured())
        {
            Result.ErrorInfo=CompilerContext->ErrorInfo;
            return Result;
        }
    }
    
    //3.try create AST from tokens;
    for(auto& FileContext : CompilerContext->ScriptFiles.Values())
    {
        BuildAST(FileContext);
        if(CompilerContext->ErrorInfo.IsErrorOccured())
        {
            Result.ErrorInfo=CompilerContext->ErrorInfo;
            return Result;
        }
    }

    //4. Semantic analysis
    auto SemanticAnalyzer = CSemanticAnalyzer();
    SemanticAnalyzer.Analysis(CompilerContext);
    if(CompilerContext->ErrorInfo.IsErrorOccured())
    {
        Result.ErrorInfo=CompilerContext->ErrorInfo;
        return Result;
    }





    //5 generate byte code
    auto ByteCodeFile =CByteCodeGenerator().CreateByteCodeFile(CompilerContext);

    Result.ByteCode=ByteCodeFile;
    Result.ErrorInfo=CompilerContext->ErrorInfo;
    return Result;
}

WHENGINE_API  TOptional<CString> WH::CompileScriptIn(const SPath &InFolder, const SPath &OutputByteCode)
{
    //compile scripts
    SPath ScriptsFolder=InFolder;
    TVector<SPath> ScriptFiles;
    //recursively find all .cs .gs files
    for(auto& p: std::filesystem::recursive_directory_iterator(ScriptsFolder.GetLowLevel()))
    {
        if(p.is_regular_file())
        {
            if(p.path().extension()==U".cs"||p.path().extension()==U".gs")
            {
                ScriptFiles.Add(SPath(p.path()));
            }
        }
    }
    auto Result=WH::Compile(ScriptFiles);
    if(Result.ErrorInfo.IsErrorOccured())
    {
        return Result.ErrorInfo.ToString();
    }

    //write byte code to build folder
    SFileUtils::SaveBinaryTo(Result.ByteCode->ByteCode,OutputByteCode);

    return TOptional<CString>();
}

WHENGINE_API TOptional<CString> WH::CompileProjectScripts(const SPath & ProjectPath)
{
    //create a build folder
    SPath BuildFolder=ProjectPath / U"Build";
    if(!BuildFolder.Exists())
    {
        auto Error=SFileUtils::CreateDirectory(BuildFolder);
        if (!Error.IsEmpty())
        {
            return Error;
        }
    }

    //compile scripts
    return WH::CompileScriptIn(ProjectPath / U"Scripts",BuildFolder / U"ByteCode.bin");
}
